package com.gpj.netty.demo.nio.base;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;

/**
 * @className NIOBaseSocketClient
 * @description
 *   基本的Reator模型（即单线程版的NIO模型） -- 客户端
 * @author GPJ
 * @date 2021/2/8 17:04
 * @version 1.0
 **/
@Slf4j
public class NIOBaseSocketClient {
	public static void main(String[] args) throws IOException {
		SocketChannel socketChannel = SocketChannel.open();
		socketChannel.configureBlocking(false);

		Selector selector = Selector.open();
		socketChannel.connect(new InetSocketAddress(9000));

		socketChannel.register(selector, SelectionKey.OP_CONNECT);

		int requestNum = 0;
		// 轮询监听事件触发
		while (true) {
			log.info("等待事件触发...");
			// 阻塞等待事件触发
			selector.select();

			// 获取触发事件的key
			Iterator<SelectionKey> keys = selector.selectedKeys().iterator();

			// 触发事件处理
			while (keys.hasNext()){
				SelectionKey key = keys.next();
				//删除本次已处理的key，防止下次select重复处理
				keys.remove();
				if (key.isConnectable()) {// 已连接事件
					log.info("【已连接】事件被触发...");
					SocketChannel channel = (SocketChannel) key.channel();
					// 如果正在连接，则完成连接
					if (channel.isConnectionPending()) {
						channel.finishConnect();
					}
					// 设置成非阻塞
					channel.configureBlocking(false);
					//在这里可以给服务端发送信息哦
					channel.write(ByteBuffer.wrap(
							"客户端第一次请求".getBytes(StandardCharsets.UTF_8)));
					//在和服务端连接成功之后，为了可以接收到服务端的信息，需要给通道设置读的权限。
					channel.register(selector, SelectionKey.OP_READ);
				} else if (key.isReadable()) {
					log.info("【读】事件被触发...");
					SocketChannel channel = (SocketChannel) key.channel();
					ByteBuffer buffer = ByteBuffer.allocate(1024);
					int len = channel.read(buffer);
					if(len != -1){
						log.info("接收到服务器响应:【{}】",
								new String(buffer.array(), 0, len, StandardCharsets.UTF_8));
					}
					// 防止死循环,接收到第二次响应之后结束进程
					requestNum++;
					if(requestNum > 1){
						break;
					}
					// 进行第二次请求
					channel.write(ByteBuffer.wrap(
							"客户端第二次请求".getBytes(StandardCharsets.UTF_8)));

					// 重新注册读/写事件监听
					key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
				} else if (key.isWritable()) {
					log.info("【写】事件被触发...");
					// 重新注册写事件监听
					key.interestOps(SelectionKey.OP_READ);
				} else {
					log.warn("未定义事件被触发...");
				}
			}

			// 防止死循环,接收到第二次响应之后结束进程
			if(requestNum > 1){
				socketChannel.close();
				break;
			}
		}
	}
}
